#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>    // 头文件 led_classdev
#include <mach/regs-gpio.h>   
#include <mach/gpio-bank.h>    // GPJ0CON  
#include <linux/io.h>
#include <linux/ioport.h>     // writel  

// qt_x210v3s_160307\kernel\arch\arm\mach-s5pv210\include\mach\gpio.h
#include <mach/gpio.h>      //gpiolib库使用

#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT

static struct led_classdev mydev1;			// 定义结构体变量
static struct led_classdev mydev2;			// 定义结构体变量
static struct led_classdev mydev3;			// 定义结构体变量


// 得到引脚号 , s5pv210所有gpio口都定义宏了， 不用计算引脚号！！
#define GPIO_LED1 S5PV210_GPJ0(3)  
#define GPIO_LED2 S5PV210_GPJ0(4)    // 176     0 *32 + 8*6 + 4 =  176     
#define GPIO_LED3 S5PV210_GPJ0(5)



// 这个函数就是要去完成具体的硬件读写任务的
// echo 1 > brightness  会调用这个方法 
/** 
    LED_OFF		= 0,       可以设置灭 
	LED_HALF	= 127,     半亮
	LED_FULL	= 255,     全亮
**/
static void s5pv210_led1_set_(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led1_set1\n");





// 一次控制3颗led
#if 0	
	writel(0x11111111, GPJ0CON);

    if (value == LED_OFF)
	{
			writel((readl(GPJ0DAT) | (1<<3) | (1<<4 ) | (1<<5) ), GPJ0DAT);
	}else{
            writel((readl(GPJ0DAT) & (~(1<<3)) & (~(1<<4 )) & (~(1<<5)) ), GPJ0DAT);    
	}
#endif

// 单独控制led
#if 0
	
	// 在这里根据用户设置的值来操作硬件
	// 用户设置的值就是value
	// 
	if (value == LED_OFF)
	{
		// 用户给了个0，希望LED灭
		//writel(0x11111111, GPJ0CON);
		// 读改写三部曲 ， 保证自己修改的不影响别人
		writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
	}
	else
	{
		// 用户给的是非0，希望LED亮
		//writel(0x11111111, GPJ0CON);
		writel((readl(GPJ0DAT) | ~(1<<3)), GPJ0DAT);
	}
#endif

}


static enum led_brightness brightness_get_(struct led_classdev *led_cdev){
       return  LED_HALF;
}

static void s5pv210_led2_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv2102_led_set\n");


	printk(KERN_INFO "s5pv210_led_set1-- gpio %d\n",GPIO_LED2);
    // 程序员实现具体逻辑操作led ， 操作寄存器 
     if(value == LED_OFF ){
      // 控制led1  读改写       灭  拉高
           gpio_set_value(GPIO_LED2, 1);
     }else {
           gpio_set_value(GPIO_LED2, 0);
     }

	

#if 0
	writel(0x11111111, GPJ0CON);
	
	// 在这里根据用户设置的值来操作硬件
	// 用户设置的值就是value
	if (value == LED_OFF)
	{
		// 用户给了个0，希望LED灭
		//writel(0x11111111, GPJ0CON);
		// 读改写三部曲
		writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
	}
	else
	{
		// 用户给的是非0，希望LED亮
		//writel(0x11111111, GPJ0CON);
		writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
	}
#endif 


}

static void s5pv210_led3_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led3_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	// 在这里根据用户设置的值来操作硬件
	// 用户设置的值就是value
	if (value == LED_OFF)
	{
		// 用户给了个0，希望LED灭
		//writel(0x11111111, GPJ0CON);
		// 读改写三部曲
		writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);
	}
	else
	{
		// 用户给的是非0，希望LED亮
		//writel(0x11111111, GPJ0CON);
		writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);
	}
}
 
// 参考 ：driver/kernel/drivers/leds/leds-s3c24xx.c     参考s3c24xx 三星写的，s3c24xx 和 s5pv210差不多功能都是三星的
/*
	const char		*name;
	int			 brightness;       亮度 
	int			 max_brightness;   最大亮度， 像补光灯一样
	int			 flags;      led是否可以挂起，恢复相关的


属性怎么来的：  kernel/drivers/leds/led-class.c 类的属性如下，
	static struct device_attribute led_class_attrs[] = {
	__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
	__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
	__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
	__ATTR_NULL,

测试：    echo  255 >  brightness  调用 led_brightness_store   中 调用函数指针   指定函数 led_classdev.s5pv210_led1_set 
cat  brightness   调用  led_brightness_show  调用函数指针   指定函数  led_classdev.brightness_get

};

*/

static int __init s5pv210_led_init(void)
{
   // led1 
	// 参数1： gpio 编号  参数2：  label标识 , kernel用于记录该gpio已经被使用，后面可以根据这个label来查找gpio 
    if(gpio_request(GPIO_LED2, "led_gpj0_4")) {
           printk(KERN_INFO "gpio_request fail \n");
           return -EINVAL;
    }
    // 设置输出模式，输出1 , 高电平， led灯灭 
    gpio_direction_output(GPIO_LED2, 1);


	// 用户insmod安装驱动模块时会调用该函数
	// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
	int ret = -1;
	
	// led1
	mydev1.name = "led1";  //驱动名字
	mydev1.brightness = 0;	
	mydev1.brightness_set = s5pv210_led1_set_;
	mydev1.brightness_get = brightness_get_;
	
	/**
	 *   使用驱动框架来做 ， 通过调用    led_classdev_register 和  led_classdev 对应结构体即可 

	 *   led_classdev  就是一个结构体， 内部通过#attr实现，可以创建节点，可以定义set/get函数，给读取节点
	 *   
     *   装载以后， 执行 led_classdev_register  ->  内部也执行  device_create(test_class, NULL, mydev, NULL, "driver_test");
     *   到 sys/class/leds目录下， 出现设备 led1  led2  led3  
     *   brightness  max_brightness   power  subsystem uevent  5个文件下面
     *
	 */ 
	ret = led_classdev_register(NULL, &mydev1);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	// led2
	mydev2.name = "led2";
	mydev2.brightness = 0;	
	mydev2.brightness_set = s5pv210_led2_set;
	
	ret = led_classdev_register(NULL, &mydev2);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	// led3
	mydev3.name = "led3";
	mydev3.brightness = 0;	
	mydev3.brightness_set = s5pv210_led3_set;
	
	ret = led_classdev_register(NULL, &mydev3);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}

    


	
	return 0;
}

static void __exit s5pv210_led_exit(void)
{

    gpio_free(GPIO_LED2);

	led_classdev_unregister(&mydev1);
	led_classdev_unregister(&mydev2);
	led_classdev_unregister(&mydev3);
	printk(KERN_INFO "s5pv210_led_exit\n");
}

// insmod *.ko 的时候回调
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");							// 描述模块的许可证
MODULE_AUTHOR("aston <1264671872@qq.com>");		// 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led");					// 描述模块的别名信息








